require "pathname"
additional_path = Pathname.new(__FILE__).realpath.dirname.join("..", "..", "..", "..")
$: << additional_path
load_paths << additional_path

require "logger"
require "fileutils"
require "set"

require "wondercap/lib/deployment/core/outer_configuration_mixin"
require "wondercap/lib/deployment/core/inner_configuration_mixin"
require "wondercap/lib/deployment/core/snapshot_transaction"
require "wondercap/lib/deployment/core/old_snapshots_cleaner"
require "wondercap/lib/utils/logging"
require "wondercap/lib/utils/syscmd"

# requires for basic objects used by strategies
require "wondercap/lib/deployment/core/deployment_list"
##

class << self
  include Wondercap::Logging
  include Wondercap::Deployment::OuterConfigurationMixin
end

define_area(:local) {
  role :default, "localhost"
}

load "wondercap/lib/multi_configuration/recipes/multi_configuration"
Dir[Pathname.new(__FILE__).realpath.dirname.join("..", "core", "strategies", "**", "*.rb")].each { |f| p f; load f}
Dir[Pathname.new(__FILE__).realpath.dirname.join("..", "core", "filters", "**", "*.rb")].each { |f| p f; load f}
load "wondercap/lib/deployment/recipes/db"
load "wondercap/lib/deployment/recipes/names"
load "wondercap/lib/deployment/recipes/debug"

role :local, "localhost"
namespace :wondercap do
  
  after "wondercap:init_multi_configuration" do
    logger.level = ::Logger::FATAL - (fetch :verbose, 4).to_i
  end
      
  after "wondercap:init_multi_configuration" do
    mc.search_paths << File.join(additional_path, "wondercap", "lib", "deployment", "core", "extensions") + "/"
    mc.search_paths << File.join(additional_path, "wondercap", "lib", "deployment", "extensions") + "/"

    mc.extension_modules << Wondercap::Deployment::InnerConfigurationMixin << Wondercap::Logging << Wondercap::Syscmd
    outer_conf = self.top
    mc.initializers << lambda { |name, conf| 
      conf.logger_device.output_devs << logger_device
      conf.logger.level = logger.level
      
      conf.instance_eval do
        set :project_name, name.to_s
        set :outer_configuration, outer_conf
        use(:base).for(:base)
        
        use(:explicit).for(:runtime_dependencies)
        use(:explicit).for(:deployment_list)

        rev = outer_conf.fetch(:revision, nil)
        use(:explicit).for(:revision) { set :revision, rev }
        use(:head).for(:revision) unless rev
        use(:generic).for(:revision)
        
        use(:copy).for(:src_patch)
        use(:copy).for(:dist_patch)

        unless outer_conf.fetch(:remote, false)
          use(:local_build).for(:deployment)
        else
          use(:copy).for(:deployment)
        end
      end
    }
  end
  
  after "wondercap:process_multi_configuration_files" do
    outer_conf = self.top
    mc.each do  |name, conf|
      conf.instance_eval do 
        unless outer_conf.fetch(:remote, false)
          if outer_conf.fetch(:force_build, false)
            use(:one_time_wrapper).for(:build)
          else
            use(:persistent_one_time_wrapper).for(:build)
          end
        else
          use(:current_local_snapshot).for(:revision)
          use(:current_local_snapshot).for(:runtime_dependencies)
        end        
        
        use(:link_scripts).for(:dist_patch)
        if outer_conf.fetch(:force_sources, false)
          use(:one_time_wrapper).for(:sources) 
        else
          use(:persistent_one_time_wrapper).for(:sources) 
        end
      end
    end
  end
  
  task "init_area_and_snapshot" do
    if fetch(:remote, false) || fetch(:area, nil)
      area_name = fetch(:area, nil) || Wondercap::Deployment::DefaultAreaFinder.new(names.map { |name| mc.get_configuration(name) }).call
      raise "area name not specified and there's no default" unless area_name
      area_name = area_name.to_s
    else
      area_name = "local"
    end
    
    raise RuntimeError.new("area '#{area_name}' is undefined") unless area_defined?(area_name)

    self.area = Wondercap::Deployment::Area.find_or_create_by_name(area_name)
    outer_conf = self
    mc.each do |name, conf|
      apply_area(area_name, conf)
    end
    
    sid = fetch :sid, nil
    if !sid
      self.snapshot = area.current_snapshot
    else
      self.snapshot = Wondercap::Snapshot::find_by_id(Integer(sid))
      raise "snapshot with specified id not found: #{sid}" unless snapshot
    end    
  end
  after "wondercap:filter_names", "wondercap:init_area_and_snapshot"
  
  after "wondercap:process_filters" do
    if top_task.fully_qualified_name == "wondercap:deploy"
      if fetch(:remote, false)
        filters << get_name_filter(:append_runtime_required)
        filters << get_name_filter(:remote_fresh_only)
      end
      filters << get_name_filter(:order_by_runtime_dependencies)
    end
  end
   
  task :inspect_snapshot do
    ui.say "area: #{snapshot.name}"
    ui.say "snapshot: id=#{snapshot.id}, created at #{snapshot.created_at}"
    snap.deployments.each do |dep|
      ui.say "\t#{dep.name}: #{dep.revision}"
      ui.say "\tdependencies:"
      ui.say "\t\t" 
      ui.say(!dep.runtime_dependencies.empty? ? ui.list(dep.runtime_dependencies, :columns_across) : "-")
      ui.say "\tstatic dependencies:"
      ui.say "\t\t"
      ui.say(!dep.static_dependencies.empty? ? ui.list(dep.static_dependencies, :columns_across) : "-")
      ui.say "\n"
    end
  end
  
  task :inspect_area do
    ui.say "#{area.name}: "
    unless area.hosts.nil? || hosts.empty?
      ui.say ui.list(hosts.map { |host| host.to_s }, :inline)
    else
      ui.say("none")
    end

    header = "#{"Id".ljust(4)} | #{"Created at".ljust(32)} | #{"Current".ljust(32)} | Unfinished"
    ui.say(header)
    ui.say("-" * header.length)
    
    area.existent_snapshots.each do |snap|
      ui.say "#{snap.id.to_s.ljust(4)} | #{snap.created_at.to_s.ljust(32)} | #{(snap == area.current_snapshot ? "X" : " ").ljust(32)} | #{(snap == area.unfinished_snapshot ? "X" : " ").ljust(32)} |"
    end
    ui.say("\n")
  end
  
  task :clean_local_builds do
    next if fetch(:remote, false)
    
    per_project_builds_limit = Integer(fetch(:per_project_builds_limit, 4))
    names.each do |name|
      conf = mc.find_configuration(name)
      conf.build_repository.sync
      conf.build_repository.remove_outdated(per_project_builds_limit)
    end
  end
  after "wondercap:deploy", "wondercap:clean_local_builds"
  
  task :clean_area do
    existent_snapshots_limit = Integer(fetch(:existent_snapshots_limit, 10))
    next if existent_snapshots_limit <= 0

    getter = lambda do |deployment|
      conf = mc.find_configuration(deployment.name)
      unless conf
        logger.warn "can't find configuration for: #{deployment.name}"
        return nil
      end
      conf.use(:explicit).for(:revision) { set :revision, deployment.revision }
    end
    Wondercap::Deployment::OldSnapshotsCleaner.clean_old_snapshots(area, existent_snapshots_limit, getter)
  end
  after "wondercap:deploy", "wondercap:clean_area"
  
  task :deploy do
    deployments = names.map do |name|
      Wondercap::Deployment::Deployment.new(:name => name, :revision => mc.find_configuration(name).real_revision)
    end
    
    used_configurations = {}
    getter = lambda do |deployment|
      used_configurations[deployment] ||= mc.find_configuration(deployment.name).clone
    end
    
    Wondercap::Deployment::SnapshotTransactionCleaner.new(getter).clean_unfinished_snapshot(area)
    begin
      Wondercap::Deployment::SnapshotTransaction.new(getter).deploy_new_snapshot(area, deployments, { :ignore_errors => fetch(:ignore_errors, false)})
      trigger :deployment_succeeded, current_task
    rescue => e
      trigger :deployment_failed, current_task
      raise e      
    end
  end

end
